package com.swift.ewm;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import org.apache.commons.lang3.StringUtils;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;


/**
 * @author zl
 * @version 1.0
 * @date 2022/2/21 16:01
 */
public class QRCodeUtil {
    /**
     * 默认宽度
     */
    private static final Integer WIDTH = 500;
    /**
     * 默认高度
     */
    private static final Integer HEIGHT = 500;

    /**
     * LOGO 默认宽度
     */
    private static final Integer LOGO_WIDTH = 40;
    /**
     * LOGO 默认高度
     */
    private static final Integer LOGO_HEIGHT = 40;

    /**
     * 图片格式
     */
    private static final String IMAGE_FORMAT = "png";

    private static final String CHARSET = "utf-8";

    /**
     * 原生转码前面没有 data:image/png;base64 这些字段，返回给前端是无法被解析
     */
    private static final String BASE64_IMAGE = "data:image/png;base64,%s";


    /**用于设置QR二维码参数
     * com.google.zxing.EncodeHintType：编码提示类型,枚举类型
     * EncodeHintType.CHARACTER_SET：设置字符编码类型
     * EncodeHintType.ERROR_CORRECTION：设置误差校正
     *      ErrorCorrectionLevel：误差校正等级，L = ~7% correction、M = ~15% correction、Q = ~25% correction、H = ~30% correction
     *      不设置时，默认为 L 等级，等级不一样，生成的图案不同，但扫描的结果是一样的
     * EncodeHintType.MARGIN：设置二维码边距，单位像素，值越小，二维码距离四周越近
     * */
    private static Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>() {

        private static final long serialVersionUID = 1L;
        {

            // 指定二维码的纠错等级为中级
            put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);// 设置QR二维码的纠错级别（H为最高级别）具体级别信息
            // 指定字符编码为utf-8
            put(EncodeHintType.CHARACTER_SET, CHARSET);// 设置编码方式
            // 设置图片的边距
            put(EncodeHintType.MARGIN, 1);
        }

    };

    /**
     * 生成二维码，使用默认尺寸
     *
     * @param content 内容
     * @return
     */
    public String getBase64QRCode(String content,String text) {
        return getBase64Image(content, WIDTH, HEIGHT, null, null, null,text);
    }

    /**
     * 生成二维码，使用默认尺寸二维码，插入默认尺寸logo
     *
     * @param content 内容
     * @param logoUrl logo地址
     * @return
     */
    public String getBase64QRCode(String content, String logoUrl,String text) {
        return getBase64Image(content, WIDTH, HEIGHT, logoUrl, LOGO_WIDTH, LOGO_HEIGHT,text);
    }

    /**
     * 生成二维码
     *
     * @param content    内容
     * @param width      二维码宽度
     * @param height     二维码高度
     * @param logoUrl    logo 在线地址
     * @param logoWidth  logo 宽度
     * @param logoHeight logo 高度
     * @return
     */
    public String getBase64QRCode(String content, Integer width, Integer height, String logoUrl, Integer logoWidth, Integer logoHeight,String text) {
        return getBase64Image(content, width, height, logoUrl, logoWidth, logoHeight,text);
    }


    private String getBase64Image(String content, Integer width, Integer height, String logoUrl, Integer logoWidth, Integer logoHeight,String text) {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        BufferedImage bufferedImage = crateQRCode(content, width, height, logoUrl, logoWidth, logoHeight);
        try {
            if(StringUtils.isNotBlank(text)){
                bufferedImage = drawTxtQRCode(bufferedImage,text);
            }
            ImageIO.write(bufferedImage, IMAGE_FORMAT, os);
        } catch (IOException e) {
            System.out.println("[生成二维码，错误"+e+"]");
        }
        // 转出即可直接使用
        return String.format(BASE64_IMAGE, Base64.getEncoder().encodeToString(os.toByteArray()));
    }

    /**
     * 将图片绘制在背景图上
     *
     * @param backgroundImage 背景图
     * @param zxingImage      图片
     * @param x               图片在背景图上绘制的x轴起点
     * @param y               图片在背景图上绘制的y轴起点
     * @return
     * @throws IOException
     */
    public BufferedImage drawImage(BufferedImage backgroundImage, BufferedImage zxingImage, int x, int y) throws IOException {
        Objects.requireNonNull(backgroundImage, ">>>>>背景图不可为空");
        Objects.requireNonNull(zxingImage, ">>>>>二维码不可为空");
        //二维码宽度+x不可以超过背景图的宽度,长度同理
        if ((zxingImage.getWidth() + x) > backgroundImage.getWidth() || (zxingImage.getHeight() + y) > backgroundImage.getHeight()) {
            throw new IOException(">>>>>二维码宽度+x不可以超过背景图的宽度,长度同理");
        }
        //合并图片
        Graphics2D g = backgroundImage.createGraphics();
        g.drawImage(zxingImage, x, y,
                zxingImage.getWidth(), zxingImage.getHeight(), null);
        return backgroundImage;
    }


    /**
     * 生成二维码
     * @param image		二维码图片信息
     * @param note			二维码 底部的文字信息 ，若为null，则二维码不带文字信息
     */
    public BufferedImage drawTxtQRCode(BufferedImage image, String note) {
        BufferedImage outImage = null;
        try {
                int width = image.getWidth();
                int height = image.getHeight();
                // 新的图片，把带logo的二维码下面加上文字
                outImage = new BufferedImage(WIDTH, HEIGHT+50, BufferedImage.TYPE_4BYTE_ABGR);
                Graphics2D outg = outImage.createGraphics();
                // 画二维码到新的面板
                outg.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null);
                // 画文字到新的面板
                outg.setColor(Color.BLACK);
                Font font = new Font(null, Font.BOLD, 30);
                outg.setFont(font); // 字体、字型、字号
                int strWidth = outg.getFontMetrics().stringWidth(note);
                while (strWidth > width){
                    int size = font.getSize() - 5 <= 0 ? 8 : font.getSize() - 5 ;
                    font = new Font(null, Font.BOLD, size);
                    outg.setFont(font);
                    strWidth = outg.getFontMetrics().stringWidth(note);
                }
                outg.drawString(note, (width - strWidth)/2, height + (outImage.getHeight() - height) / 2 + 12); // 画文字
                outg.dispose();
                outImage.flush();

        } catch (Exception e) {
            e.printStackTrace();
        }
        return outImage;
    }


    /**
     * 将文字绘制在背景图上
     *
     * @param backgroundImage 背景图
     * @param x               文字在背景图上绘制的x轴起点
     * @param y               文字在背景图上绘制的y轴起点
     * @return
     * @throws IOException
     */
    public static BufferedImage drawString(BufferedImage backgroundImage, String text, int x, int y,Color color) {
        //绘制文字
        Graphics2D g = backgroundImage.createGraphics();
        //设置颜色
        g.setColor(color);
        //消除锯齿状
        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
        Font font = new Font("微软雅黑", Font.BOLD, 10);
        //设置字体
        g.setFont(font);
        //绘制文字
        g.drawString(text, x, y);
        return backgroundImage;
    }

    /**
     * 生成二维码
     *
     * @param content    内容
     * @param width      二维码宽度
     * @param height     二维码高度
     * @param logoUrl    logo 在线地址
     * @param logoWidth  logo 宽度
     * @param logoHeight logo 高度
     * @return
     */
    private BufferedImage crateQRCode(String content, Integer width, Integer height, String logoUrl, Integer logoWidth, Integer logoHeight) {
        if (StringUtils.isNotBlank(content)) {
            ServletOutputStream stream = null;
            try {
                MultiFormatWriter writer = new MultiFormatWriter();
                /**
                 * MultiFormatWriter:多格式写入，这是一个工厂类，里面重载了两个 encode 方法，用于写入条形码或二维码
                 *      encode(String contents,BarcodeFormat format,int width, int height,Map<EncodeHintType,?> hints)
                 *      contents:条形码/二维码内容
                 *      format：编码类型，如 条形码，二维码 等
                 *      width：码的宽度
                 *      height：码的高度
                 *      hints：码内容的编码类型
                 * BarcodeFormat：枚举该程序包已知的条形码格式，即创建何种码，如 1 维的条形码，2 维的二维码 等
                 * BitMatrix：位(比特)矩阵或叫2D矩阵，也就是需要的二维码
                 */
                BitMatrix bitMatrix = writer.encode(content, BarcodeFormat.QR_CODE, width, height, hints);
                /**参数顺序分别为：编码内容，编码类型，生成图片宽度，生成图片高度，设置参数
                 * java.awt.image.BufferedImage：具有图像数据的可访问缓冲图像，实现了 RenderedImage 接口
                 * BitMatrix 的 get(int x, int y) 获取比特矩阵内容，指定位置有值，则返回true，将其设置为前景色，否则设置为背景色
                 * BufferedImage 的 setRGB(int x, int y, int rgb) 方法设置图像像素
                 *      x：像素位置的横坐标，即列
                 *      y：像素位置的纵坐标，即行
                 *      rgb：像素的值，采用 16 进制,如 0xFFFFFF 白色
                 */
                BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
                for (int x = 0; x < width; x++) {
                    for (int y = 0; y < height; y++) {
                        bufferedImage.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
                    }
                }
                if (StringUtils.isNotBlank(logoUrl)) {
                    insertLogo(bufferedImage, width, height, logoUrl, logoWidth, logoHeight);
                }
                return bufferedImage;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (stream != null) {
                    try {
                        stream.flush();
                        stream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return null;
    }
    /**
     * 二维码插入logo
     *
     * @param source     二维码
     * @param width      二维码宽度
     * @param height     二维码高度
     * @param logoUrl    logo 在线地址
     * @param logoWidth  logo 宽度
     * @param logoHeight logo 高度
     * @throws Exception
     */
    private void insertLogo(BufferedImage source, Integer width, Integer height, String logoUrl, Integer logoWidth, Integer logoHeight) throws Exception {
        // logo 源可为 File/InputStream/URL
        Image src = null;
        if(logoUrl.indexOf("http") > -1){
            src = ImageIO.read(new URL(logoUrl));
        }else{
            src = ImageIO.read(new File(logoUrl));
        }
        // 插入LOGO
        Graphics2D graph = source.createGraphics();
        int x = (width - logoWidth) / 2;
        int y = (height - logoHeight) / 2;
        graph.drawImage(src, x, y, logoWidth, logoHeight, null);
        Shape shape = new RoundRectangle2D.Float(x, y, logoWidth, logoHeight, 6, 6);
        graph.setStroke(new BasicStroke(3f));
        graph.draw(shape);
        graph.dispose();
    }


    /**
     * 获取二维码
     *
     * @param content 内容
     * @param output  输出流
     * @throws IOException
     */
    public void getQRCode(String content, OutputStream output,String text) throws IOException {
        BufferedImage image = crateQRCode(content, WIDTH, HEIGHT, null, null, null);
        ImageIO.write(image, IMAGE_FORMAT, output);
    }

    /**
     * 获取二维码
     *
     * @param content 内容
     * @param logoUrl logo资源
     * @param output  输出流
     * @throws Exception
     */
    public void getQRCode(String content, String logoUrl, OutputStream output,String text) throws Exception {
        BufferedImage image = crateQRCode(content, WIDTH, HEIGHT, logoUrl, LOGO_WIDTH, LOGO_HEIGHT);
        ImageIO.write(image, IMAGE_FORMAT, output);
    }
}
